﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace DiscoveryLogic.Workflows.Components.Markov.MaxEnt
{
    /// <summary>
    /// A writer for GIS models that saves models in a binary format. This format is not the one
    /// used by the java version of MaxEnt
    /// It has two main differences, designed for performance when loading the data
    /// from file: first, it uses big endian data values, which is native for C#, and secondly it
    /// encodes the outcome patterns and values in a more efficient manner.
    /// </summary>
    public class BinaryGisModelWriter : GisModelWriter
    {
        private Stream mOutput;
        private byte[] mBuffer = new byte[7];
        private System.Text.Encoding mEncoding = System.Text.Encoding.UTF8;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public BinaryGisModelWriter()
        {
        }

        /// <summary>
        /// Takes a GIS model and a file and
        /// writes the model to that file.
        /// </summary>
        /// <param name="model">
        /// The GisModel which is to be persisted.
        /// </param>
        /// <param name="fileName">
        /// The full path and name of the file in which the model is to be persisted.
        /// </param>
        public void Persist(GisModel model, string fileName)
        {
            using (mOutput = new FileStream(fileName, FileMode.Create))
            {
                base.Persist(model);
            }
        }

        /// <summary>
        /// Takes a GIS model and a Stream and
        /// writes the model to that Stream.
        /// </summary>
        /// <param name="model">
        /// The GIS model which is to be persisted.
        /// </param>
        /// <param name="dataOutputStream">
        /// The Stream which will be used to persist the model.
        /// </param>
        public void Persist(GisModel model, Stream dataOutputStream)
        {
            using (mOutput = dataOutputStream)
            {
                base.Persist(model);
            }
        }

        /// <summary>
        /// Writes a UTF-8 encoded string to the model file.
        /// </summary>
        /// <param name="data">
        /// The string data to be persisted.
        /// </param>
        protected override void WriteString(string data)
        {
            mOutput.WriteByte((byte)mEncoding.GetByteCount(data));
            mOutput.Write(mEncoding.GetBytes(data), 0, mEncoding.GetByteCount(data));
        }

        /// <summary>
        /// Writes a 32-bit signed integer to the model file.
        /// </summary>
        /// <param name="data">
        /// The integer data to be persisted.
        /// </param>
        protected override void WriteInt32(int data)
        {
            mBuffer = BitConverter.GetBytes(data);
            mOutput.Write(mBuffer, 0, 4);
        }

        /// <summary>
        /// Writes a double-precision floating point number to the model file.
        /// </summary>
        /// <param name="data">
        /// The floating point data to be persisted.
        /// </param>
        protected override void WriteDouble(double data)
        {
            mBuffer = BitConverter.GetBytes(data);
            mOutput.Write(mBuffer, 0, 8);
        }

        /// <summary>
        /// Writes the predicate data to the file in a more efficient format to that implemented by
        /// GisModelWriter.
        /// </summary>
        /// <param name="model">
        /// The GIS model containing the predicate data to be persisted.
        /// </param>
        protected override void WritePredicates(GisModel model)
        {
            int[][] outcomePatterns = model.GetOutcomePatterns();
            PatternedPredicate[] predicates = GetPredicates();

            //write the number of outcome patterns
            WriteInt32(outcomePatterns.Length);

            //write the number of predicates
            WriteInt32(predicates.Length);

            int currentPredicate = 0;

            for (int currentOutcomePattern = 0; currentOutcomePattern < outcomePatterns.Length; currentOutcomePattern++)
            {
                //write how many outcomes in this pattern
                WriteInt32(outcomePatterns[currentOutcomePattern].Length);

                //write the outcomes in this pattern (the first value contains the number of predicates in the pattern
                //rather than an outcome)
                for (int currentOutcome = 0; currentOutcome < outcomePatterns[currentOutcomePattern].Length; currentOutcome++)
                {
                    WriteInt32(outcomePatterns[currentOutcomePattern][currentOutcome]);
                }

                //write predicates for this pattern
                while (currentPredicate < predicates.Length && predicates[currentPredicate].OutcomePattern == currentOutcomePattern)
                {
                    WriteString(predicates[currentPredicate].Name);
                    for (int currentParameter = 0; currentParameter < predicates[currentPredicate].ParameterCount; currentParameter++)
                    {
                        WriteDouble(predicates[currentPredicate].GetParameter(currentParameter));
                    }
                    currentPredicate++;
                }
            }
        }

    }
}
